#pragma once

#ifndef LFMT_UTILS_HPP_
#define LFMT_UTILS_HPP_

#include <array>
#include <cstdint>
#include <string_view>
#include <type_traits>

namespace lfmt {
namespace detail {
inline bool is_align(const char ch) { return ch == '<' || ch == '^' || ch == '>'; }
enum class Align : char {
    left = '<',   // 左对齐(默认)
    center = '^', // 居中对齐
    right = '>',  // 右对齐
};

inline bool is_sign(const char ch) { return ch == '+' || ch == '-' || ch == ' '; }
enum class Sign : char {
    plus = '+',  // 强制在结果之前显示符号, 正数前面显示"+", 负数前面显示"-"
    minus = '-', // 不强制在结果之前显示符号, 即只有负数前面会显示"-"(默认)
    space = ' ', // 正数前面插入空格, 负数无变化
};

inline bool is_type(const char ch) {
    return ch == 'b' || ch == 'o' || ch == 'd' || ch == 'x' || ch == 's' || ch == 'c' || ch == 'f' || ch == 'e' ||
           ch == 'g' || ch == 'p';
}
enum class Type : char {
    bol = 's', // 布尔值或字符串

    chr = 'c', // 字符

    bin = 'b', // 二进制
    oct = 'o', // 八进制
    dec = 'd', // 十进制
    hex = 'x', // 十六进制

    fixed = 'f',   // 普通浮点数
    exp = 'e',     // 科学计数法
    general = 'g', // 通过范围选择浮点数的打印方式, 自动精度

    ptr = 'p', // 地址, 输出格式为十六进制

    none = 'n', // 格式化字符串中缺省时为此值, 会转变为对应的默认值
};

/**
 * @brief 格式化字符串中的格式化输出信息
 *
 * @note 格式为 {[index][:[fill][align][sign][width][.precision][type]]}
 */
struct Options {
    char fill = ' ';           // 填充字符(仅在width大于原输出宽度时有效)
    Align align = Align::left; // 对齐方式(仅在width大于原输出宽度时有效)
    Sign sign = Sign::minus;   // 符号位
    uint8_t width = 0;         // 输出宽度(仅在width大于原输出宽度时有效)
    bool upper = false;        // 大写输出
    int8_t precision = -1;     // 精度(仅浮点型数据有效, 负值为自动精度)
    Type type = Type::none;    // 输出类型
};

struct Context {
    std::string_view text;
    std::string_view::iterator colon;

    [[nodiscard]] Options parse() const {
        Options options;
        if (colon == nullptr || !(colon >= text.begin() && colon + 1 < text.end() - 1) ||
            !(*text.begin() == '{' && *(text.end() - 1) == '}' && *colon == ':'))
        {
            return options;
        }

        auto iter = colon + 1;
        if (iter + 1 < text.end() - 1 && is_align(*(iter + 1)))
        {
            options.fill = *iter;
            options.align = static_cast<Align>(*(iter + 1));
            if ((iter += 2) >= text.end() - 1)
            {
                goto END;
            }
        } else if (is_align(*iter))
        {
            options.align = static_cast<Align>(*iter);
            if (++iter >= text.end() - 1)
            {
                goto END;
            }
        }

        if (is_sign(*iter))
        {
            options.sign = static_cast<Sign>(*iter);
            if (++iter >= text.end() - 1)
            {
                goto END;
            }
        }

        if (isdigit(*iter))
        {
            options.width = strtol(iter, const_cast<char **>(&iter), 10);
            if (iter >= text.end() - 1)
            {
                goto END;
            }
        }

        if (*iter == '.')
        {
            options.precision = static_cast<int8_t>(strtol(iter + 1, const_cast<char **>(&iter), 10));
            if (iter >= text.end() - 1)
            {
                goto END;
            }
        }

        if (auto type = static_cast<char>(tolower(*iter)); is_type(type))
        {
            options.type = static_cast<Type>(type);
            options.upper = isupper(*iter);
        }

        if (options.precision < 0 && (options.type == Type::exp || options.type == Type::fixed))
        {
            options.precision = 6;
        }

    END:
        return options;
    }
};

template <typename T>
static constexpr bool is_char_v =
    std::is_same_v<std::decay_t<T>, char> || std::is_same_v<std::decay_t<T>, unsigned char> ||
    std::is_same_v<std::decay_t<T>, signed char>;

template <typename T> static constexpr bool is_cstring_v = false;
template <typename T> static constexpr bool is_cstring_v<T *> = is_char_v<std::decay_t<T>>;
template <typename T, size_t sz> static constexpr bool is_cstring_v<T[sz]> = is_char_v<std::decay_t<T>>;

template <typename T>
using type_choose_t = std::conditional_t<sizeof(std::decay_t<T>) <= sizeof(void *), std::decay_t<T>, const T &>;

template <typename src, typename dest, typename = void> static constexpr bool static_cast_check_t = false;
template <typename src, typename dest>
static constexpr bool static_cast_check_t<src, dest, std::void_t<decltype(static_cast<dest>(std::declval<src>()))>> =
    true;

template <size_t sz> static constexpr std::array<uint64_t, sz> exp10array() {
    uint64_t exp10 = 1;
    std::array<uint64_t, sz> array{};
    for (size_t i = 0; i < sz; ++i, exp10 *= 10)
    {
        array[i] = exp10;
    }
    return array;
}

template <class out_fct_wrap_t> bool out(out_fct_wrap_t out_fct_wrap, const Options &options, std::string_view string) {
    if (options.width <= string.size())
    {
        for (auto ch : string)
        {
            out_fct_wrap(ch);
        }
        return true;
    }

    switch (options.align)
    {
    case Align::left:
        for (auto ch : string)
        {
            out_fct_wrap(ch);
        }
        for (size_t i = 0; i < options.width - string.size(); ++i)
        {
            out_fct_wrap(options.fill);
        }
        return true;

    case Align::center:
        for (size_t i = 0; i < (options.width - string.size()) / 2; ++i)
        {
            out_fct_wrap(options.fill);
        }
        for (auto ch : string)
        {
            out_fct_wrap(ch);
        }
        for (size_t i = 0; i < (options.width - string.size() + 1) / 2; ++i)
        {
            out_fct_wrap(options.fill);
        }
        return true;

    case Align::right:
        for (size_t i = 0; i < options.width - string.size(); ++i)
        {
            out_fct_wrap(options.fill);
        }
        for (auto ch : string)
        {
            out_fct_wrap(ch);
        }
        return true;

    default:
        return false;
    }
}

template <typename Integer>
std::enable_if_t<std::is_integral_v<Integer>, std::string_view>
itostr(Integer number, char *buffer, size_t sz, int radix = 10, bool upper = false, Sign sign = Sign::minus) {
    if (buffer == nullptr || (radix <= 1 || radix > 36))
    {
        return nullptr;
    }

    bool negative = number < 0;
    number = negative ? -number : number;
    auto iter = buffer;
    uint64_t div = 1;
    auto c = upper ? 'A' : 'a';

    if (negative)
    {
        *iter++ = '-';
    } else if (sign != Sign::minus)
    { *iter++ = static_cast<char>(sign); }

    while (number >= div)
    {
        div *= radix;
    }
    while ((div /= radix) != 0 && iter - buffer < sz)
    {
        auto n = number / div % radix;
        *iter++ = n < 10 ? (n + '0') : n - 10 + c;
    }

    return std::string_view(buffer, iter - buffer);
}

} // namespace detail
} // namespace lfmt

#endif // !LFMT_UTILS_HPP_
